package com.fasteat.menu.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baidu.aip.contentcensor.AipContentCensor;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasteat.common.entity.GlobalException;
import com.fasteat.common.entity.Result;
import com.fasteat.common.entity.StatusCode;
import com.fasteat.menu.async.MenuAsync;
import com.fasteat.menu.entity.Material;
import com.fasteat.menu.entity.Menu;
import com.fasteat.menu.entity.client.MenuSearchMap;
import com.fasteat.menu.entity.client.Page;
import com.fasteat.menu.entity.client.SearchCondition;
import com.fasteat.menu.entity.vo.*;
import com.fasteat.menu.mapper.MaterialMapper;
import com.fasteat.menu.mapper.MenuMapper;
import com.fasteat.menu.service.MenuService;
import com.fasteat.menu.service.client.SearchClient;
import com.fasteat.menu.service.client.UserClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author run
 * @since 2021-01-19
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private MaterialMapper materialMapper;

    @Autowired
    private MenuAsync menuAsync;

    @Autowired
    private SearchClient searchClient;

    @Autowired
    private UserClient userClient;

    @Autowired
    private AipContentCensor censor;

    @Autowired
    private RedisTemplate redisTemplate;

    private final static String MENUKEY = "menuAllCount_";


    @Transactional
    public boolean createMenu(Menu menu){
        //进行标题和内容审核
        checkContext(menu.getMenuTitle() + menu.getMenuContext());

        //id手动设置，以便下面插入连接信息使用
        menu.setId(IdWorker.getIdStr());
        List<Material> materials = menu.getMaterials();
        materials = materials == null ? new ArrayList<>() : materials;
        int result = menuMapper.insert(menu);

        for (Material material : materials) {

            Map<String,Object> map = new HashMap<>(1,1);
            map.put("material_name",material.getMaterialName());

            List<Material> materialsByName = materialMapper.selectByMap(map);
            if (materialsByName.isEmpty()){
                material.setId(IdWorker.getIdStr());
                if (materialMapper.insert(material) < 1){
                    throw new GlobalException(StatusCode.ERROR,"用料添加失败");
                }
            }
            if (materialMapper.addMaterialOfMenuByName(menu.getId(), material.getMaterialName(), material.getDosage()) == 0){
                throw new GlobalException(StatusCode.ERROR,"用料添加失败");
            }
        }

        if (result > 0){
            menuAsync.setTagOfMenu(menu);
        }

        return result > 0;
    }

    @Transactional
    public boolean updateMenu(Menu menu){
        if (StringUtils.isEmpty(menu.getMenuCover())){
            throw new GlobalException(StatusCode.ERROR,"菜谱封面不能为空");
        }
        //进行标题和内容审核
        checkContext(menu.getMenuTitle() + menu.getMenuContext());

        //修改用料表的操作
        List<Material> materials = menu.getMaterials();
        materials = materials == null ? new ArrayList<>() : materials;
        List<Material> materialsOfMenu = materialMapper.findMaterialsOfMenu(menu.getId());
        //获取插入的用料名称表
        Set<String> newName = materials.stream().map((m) -> m.getMaterialName()).collect(Collectors.toSet());
        //获取原来的用料名称表
        Set<String> originName = materialsOfMenu.stream().map((m)->m.getMaterialName()).collect(Collectors.toSet());

        materials.forEach((m)->{
            Map<String,Object> map = new HashMap<>(1,1);
            map.put("material_name",m.getMaterialName());

            //如果用料是原来菜谱中没有的情况
            if (!originName.contains(m.getMaterialName())){
                List<Material> materialByName = materialMapper.selectByMap(map);
                //如果数据库表不存在该用料,则新增该用料
                if (materialByName.isEmpty()){
                    //id置空 等待mybatis-plus填充
                    m.setId(null);
                    materialMapper.insert(m);
                }
                materialMapper.addMaterialOfMenuByName(menu.getId(),m.getMaterialName(),m.getDosage());
            }else {
                materialMapper.updateDosageByName(menu.getId(),m.getMaterialName(),m.getDosage());
            }
        });

        materialsOfMenu.forEach((m)->{
            //如果菜谱中原来有该用料，但现在没有了
            if (!newName.contains(m.getMaterialName())){
                materialMapper.removeMaterialOfMenuByName(menu.getId(),m.getMaterialName());
            }
        });

        int result = menuMapper.updateById(menu);

        if (result > 0){
            menuAsync.setTagOfMenu(menu);
        }

        return result > 0;
    }

    public boolean deleteMenu(String menuId){
        boolean result = menuMapper.deleteById(menuId) > 0;

        //删除连接表信息和logstash记录
        if (result){
            menuAsync.removeMaterialOfMenu(menuId);
            searchClient.deleteMenuById(menuId);
        }

        return result;
    }

    public IPage<Menu> searchMenus(String keyword, Integer page,Integer size){
        IPage<Menu> menuPage
                = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page,size);

        QueryWrapper<Menu> wrapper = new QueryWrapper<>();
        keyword = keyword.trim();
        if (StrUtil.isNotEmpty(keyword)){
            wrapper.like("menu_title", keyword)
                    .or()
                    .like("menu_context", keyword);
        }

        menuPage = menuMapper.selectPage(menuPage, wrapper);

        List<Menu> menus = menuPage.getRecords();

        if (menus.isEmpty()){
            return menuPage;
        }

        Set<String> authorIds = menus.stream().map(Menu::getAuthorId).collect(Collectors.toSet());
        Result clientResult = userClient.getAuthorVos(new ArrayList<>(authorIds));

        if (clientResult.getStatusCode() != StatusCode.OK.getCode()){
            throw new GlobalException(StatusCode.ERROR,"获取用户数据失败");
        }

        Map<String, AuthorVo> authorVos = JSONArray.parseArray(JSON.toJSONString(clientResult.getData()), AuthorVo.class)
                                                .stream()
                                                .collect(Collectors.toMap(AuthorVo::getAuthorId, authorVo -> authorVo));

        //查询不到菜谱作者信息时的默认对象
        AuthorVo defaultAuthorVo = new AuthorVo();
        defaultAuthorVo.setAuthorName("该用户状态异常");

        menus.forEach(menuVo -> {
            AuthorVo authorVo = authorVos.getOrDefault(menuVo.getAuthorId(),defaultAuthorVo);
            menuVo.setAuthorName(authorVo.getAuthorName());
            menuVo.setAuthorAvatar(authorVo.getAuthorAvatar());
        });

        return menuPage;
    }

    public List<Menu> searchMenus(){
        List<Menu> menus = menuMapper.selectByMap(null);
        menus.forEach((menu -> menu.setMaterials(materialMapper.findMaterialsOfMenu(menu.getId()))));
        return menus;
    }


    //文本审核方法
    public void checkContext(String context){
        Map<String, Object> map = censor.textCensorUserDefined(context).toMap();
        if (!map.containsKey("conclusionType") || !map.get("conclusionType").equals(1)) {
            List<Object> errors = new ArrayList<>();
            List datas = (ArrayList)(map.get("data"));
            datas.forEach((data)-> errors.add(((HashMap)data).get("msg")));
            throw new GlobalException(StatusCode.ERROR,JSON.toJSONString(errors));
        }
    }

    public Menu searchMenuById(String menuId,String userId) {
        Menu menu = menuMapper.selectById(menuId);

        if (menu != null){
            Future<AuthorVo> future = menuAsync.getAuthorOfMenu(menu.getAuthorId());
            menu.setMaterials(materialMapper.findMaterialsOfMenu(menuId));
            while (!future.isDone()){
                Thread.yield();
            }

            try {
                AuthorVo authorVo = future.get();
                if (authorVo == null){
                    menu.setAuthorName("该用户状态异常");
                }else {
                    menu.setAuthorName(authorVo.getAuthorName());
                    menu.setAuthorAvatar(authorVo.getAuthorAvatar());
                }
            } catch (Exception e) {
                throw new GlobalException(StatusCode.ERROR,"获取失败");
            }
        }

        return menu;
    }

    @Override
    public IPage<Menu> searchMenuOfUser(String userId, Integer page, Integer size) {
        IPage<Menu> menuPage =
                new com.baomidou.mybatisplus.extension.plugins.pagination.Page(page,size);
        QueryWrapper<Menu> wrapper = new QueryWrapper<>();
        wrapper.eq("author_id",userId);

        menuPage = menuMapper.selectPage(menuPage, wrapper);
        menuPage.getRecords().forEach((menu -> menu.setMaterials(materialMapper.findMaterialsOfMenu(menu.getId()))));

        return menuPage;
    }

    @Override
    public Page searchMenuOfKeyWord(String keyword, Integer page, Integer size, MenuSearchMap searchMap) {
        HashMap<String,String> entry = new HashMap<>(3,1);
        entry.put("menuTitle",keyword);
        entry.put("menuTag",keyword);
        entry.put("materialName",keyword);
        return searchMenuOfCondition(new SearchCondition(entry,size,page),searchMap);
    }

    @Override
    public Page searchMenuOfMaterial(MenuSearchVo menuSearchVo,Integer page, Integer size) {
        MenuSearchMap searchMap = menuSearchVo.getSearchMap();
        StringBuilder sb = new StringBuilder();
        menuSearchVo.getMaterials().forEach((material)->{
            sb.append(material);
            sb.append(',');
        });
        //拼接后的菜谱
        String material = sb.toString();
        material = material.length() > 0 ? material : material.substring(0,material.length() - 1);
        HashMap<String,String> entry = new HashMap<>(1,1);
        entry.put("materialName",material);
        return searchMenuOfCondition(new SearchCondition(entry,size,page),searchMap);
    }

    @Override
    public List<CategoryVo> getMenuCategory() {
        CategoryVo rootCategory = new CategoryVo();
        CountDownLatch count = new CountDownLatch(1);
        menuAsync.getChildCategory(rootCategory,count);

        try {
            count.await(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new GlobalException(StatusCode.ERROR,"获取失败");
        }

        return rootCategory.getChildrens();
    }

    @Override
    public Integer getUserPraise(String userId) {
        List<MenuPraiseVo> menuPraise = menuMapper.getUserPraise(userId);
        CountDownLatch count = new CountDownLatch(menuPraise.size());
        menuPraise.forEach(menu -> {
            menuAsync.updateMenuPraise(menu,count);
        });

        try {
            count.await(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new GlobalException(StatusCode.ERROR,"获取失败");
        }

        Integer sum = menuPraise.stream()
                                .map(MenuPraiseVo::getMenuPraise)
                                .collect(Collectors.toList())
                                .stream()
                                .reduce(Integer::sum)
                                .orElse(0);

        return sum;
    }

    @Override
    public MenuMessageVo getMenuMessage(String menuId) {
        return menuMapper.getMenuMessage(menuId);
    }

    //通过筛选条件搜索菜谱
    private Page searchMenuOfCondition(SearchCondition condition, MenuSearchMap searchMap){
        Result resultOfSearch;
        if (searchMap == null){
            resultOfSearch = searchClient.searchMenuDefault(condition);
        }else {
            resultOfSearch = searchClient.searchMenu(condition
                    ,searchMap.getCommentSort()
                    ,searchMap.getPraiseSort()
                    ,searchMap.getTimeSort());
        }

        if (resultOfSearch.getStatusCode() != StatusCode.OK.getCode()){
            throw new GlobalException(StatusCode.ERROR,"获取失败");
        }

        Page retVal = JSON.parseObject(JSON.toJSONString(resultOfSearch.getData()), Page.class);
        List<MenuVo> menus = JSON.parseArray(JSON.toJSONString(retVal.getRecords()),MenuVo.class);

        if (menus.isEmpty()){
            return retVal;
        }

        Result resultOfUser = userClient.getAuthorVos(
                menus.stream().map(MenuVo::getAuthorId).collect(Collectors.toList()));
        if (resultOfUser.getStatusCode() != StatusCode.OK.getCode()){
            throw new GlobalException(StatusCode.ERROR,"获取失败");
        }

        Map<String, AuthorVo> authorVos = JSON.parseArray(JSON.toJSONString(resultOfUser.getData()), AuthorVo.class)
                .stream()
                .collect(Collectors.toMap(AuthorVo::getAuthorId, authorVo -> authorVo));
        //查询不到菜谱作者信息时的默认对象
        AuthorVo defaultAuthorVo = new AuthorVo();
        defaultAuthorVo.setAuthorName("该用户状态异常");

        menus.forEach(menuVo -> {
            AuthorVo authorVo = authorVos.getOrDefault(menuVo.getAuthorId(),defaultAuthorVo);
            menuVo.setAuthorName(authorVo.getAuthorName());
            menuVo.setAuthorAvatar(authorVo.getAuthorAvatar());
        });

        retVal.setRecords(menus);
        return retVal;
    }

    @Override
    public List<SimpleMenuVo> searchSimpleMenuOfIds(List<String> menuIds) {
        if (CollectionUtils.isEmpty(menuIds)){
            throw new GlobalException(StatusCode.ERROR,"传入参数不合法");
        }
        return menuMapper.getSimpleMenuVosOfIds(menuIds);
    }

    @Override
    public void updateMenuMessage(List<MenuMessageVo> menuMessageVos) {
        menuMessageVos.forEach(menuMessageVo -> {
            try {
                menuAsync.updateMenuMessage(menuMessageVo);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}
